//-----------------------------------------------------------------------------
//
// proxy.c
//
// Group:
//    Doug Kline,          dakline@ncsu.edu
//    Mauricio Del Carpio, mgdelcar@ncsu.edu
//    Josh Jodrey,         jdjodrey@ncsu.edu
//
//
// Provides the basic entry point for the proxy server.
//
//-----------------------------------------------------------------------------

#include <linux/string.h>
#include <linux/slab.h>

#include "ksocket.h"
#include "proxy.h"
#include "reqHandler.h"

extern struct Config g_config;
extern int g_serverDied;

ksocket_t g_socket_p  = NULL;
static struct sockaddr_in g_myAddr;


static int proxyServerInit(void);
static int proxyServerRun(void);
static void proxyServerExit(void);

//----------------------------------------------------------------------------
//
// runServer()
//
// Entry point for the kcache server thread
//
//----------------------------------------------------------------------------
int runServer(void *arg)
{
   int err;
   err = getConfig();
   if (err)
   {
      printk(KERN_ERR "ERROR: failed to get the kcache configuration\n");
      g_serverDied = 1;
      return(-1);
   }

   // Build the proxy server
   err = proxyServerInit();
   if (err)
   {
      printk(KERN_ERR "ERROR: failed to initialize the kcache server\n");
      g_serverDied = 1;
      return(-1);
   }

   err = proxyServerRun();
   if (err)
   {
      printk(KERN_ERR "ERROR: failed to run the kcache server\n");
      g_serverDied = 1;
      return(-1);
   }
 
   proxyServerExit();

   g_serverDied = 1;

   printk(KERN_DEBUG "Exiting kcache server thread\n");
  
   return(0);
   
} // end runServer()


//-----------------------------------------------------------------------------
// proxyServerInit()
//
//-----------------------------------------------------------------------------
int proxyServerInit(void)
{
   int err;
   int window_size = 16 * 1024;
   int reuse = 1;

   memset( &g_myAddr, 0, sizeof(g_myAddr));
   
   //--------------------------------------------------------------------------
   // Create a socket
   g_socket_p = ksocket( AF_INET, SOCK_STREAM, 0 );

   if (g_socket_p == NULL)
   {
      printk(KERN_INFO "ERROR: failed to create the socket\n");
      return(-1);
   }

   //--------------------------------------------------------------------------
   // Set the socket options
   ksetsockopt(g_socket_p, SOL_SOCKET, SO_SNDBUF, 
               (char *)&window_size, sizeof(window_size));

   ksetsockopt(g_socket_p, SOL_SOCKET, SO_RCVBUF, 
               (char *)&window_size, sizeof(window_size));

   ksetsockopt(g_socket_p, SOL_SOCKET, SO_REUSEADDR, 
               (char *)&reuse, sizeof(reuse));


   //--------------------------------------------------------------------------
   // Bind to the socket
   g_myAddr.sin_family      = AF_INET;
   g_myAddr.sin_addr.s_addr = g_config.proxyAddr.s_addr;
   g_myAddr.sin_port        = htons(g_config.proxyPort);

   err = kbind( g_socket_p, (struct sockaddr *) &g_myAddr, sizeof(struct sockaddr_in) );

   if (err)
   {
      printk(KERN_INFO "ERROR: failed to bind to the socket\n");
      kclose(g_socket_p);
      g_socket_p = NULL;
      return(-1);
   }

   //--------------------------------------------------------------------------
   // Listen on the socket

   err = klisten (g_socket_p, MAX_LISTEN);

   if (err == -1)
   {
      printk(KERN_INFO "ERROR: failed to listen to the socket\n");
      return(-1);
   }
   
   return 0;

} // end proxyServerInit()



//-----------------------------------------------------------------------------
// proxyServerExit()
//
//-----------------------------------------------------------------------------
void proxyServerExit(void)
{
   if (g_socket_p != NULL)
   {
      printk(KERN_INFO "closing kcache server\n");
      kshutdown(g_socket_p, 2); // SHUT_RDWR == 2
      kclose(g_socket_p);
      g_socket_p = NULL;
   }
}



//-----------------------------------------------------------------------------
// proxyServerRun()
//
// The program stays in this routine forever waiting for client requests which
// are then passed off to the reqHandler class for handling.
//
//-----------------------------------------------------------------------------
int proxyServerRun(void)
{
   int clientLength;
   ksocket_t clientFD;
   void *void_p;
   struct ClientInfo *clientInfo_p;
   
   printk(KERN_INFO "Listening on port = %d\n", g_config.proxyPort);

   while(1)
   {
      struct sockaddr_in clientAddr;
      memset( &clientAddr, 0, sizeof(clientAddr));
      clientLength = sizeof(struct sockaddr_in);

       set_current_state(TASK_INTERRUPTIBLE);
      // Wait for the next client request
      clientFD = kaccept(g_socket_p, (struct sockaddr *)&clientAddr, &clientLength);
      
      if (kthread_should_stop())
      {
         proxyServerExit();
         break;
      }

      if (clientFD == NULL)
      {
         printk(KERN_INFO "ERROR kaccept returned NULL\n");
         return(-1);
      }
      
      if(debug) printk(KERN_INFO "accept() completed, client IP=%s, port=%d\n", inet_ntoa(clientAddr.sin_addr), ntohs(clientAddr.sin_port));


      // Create a new thread and pass the clientFD to the handler.  The handler is responsible for
      // closing that File Descriptor and for deleting the passed in ClientInfo.
      void_p = kmalloc(sizeof(struct ClientInfo), GFP_KERNEL);
         
      if (void_p == NULL)
      {
         printk(KERN_INFO "ERROR: failed to allocate a ClientInfo structure\n");
         return(-1);
      }
      
      clientInfo_p = (struct ClientInfo *)void_p;
      clientInfo_p->thread_p   = 0;
      clientInfo_p->clientFD   = clientFD;
      clientInfo_p->clientAddr = clientAddr;

      // Start a handler thread
      clientInfo_p->thread_p = kthread_create(reqHandlerCreate, (void*)clientInfo_p, "%s", "handler");

      if (IS_ERR(clientInfo_p->thread_p))
      {
         printk(KERN_ERR "ERROR: failed to start kcache client handler task\n");
      }
      else
      {
         wake_up_process(clientInfo_p->thread_p);
      }

   } // end while(1)
   
   return 0;

} // end proxyServerRun()



